﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Workflow.Runtime;
using CommonWFLibrary;
using ClientWorkflowLibrary.DTRender;
using System.ServiceModel;
using System.Threading;

namespace ClientWorkflowLibrary
{
    /// <summary>
    /// Manages the WF and callbacks to render
    /// deep talks
    /// </summary>
	public class RenderWFManager
	{

        /// <summary>
        /// The callback to be given to the WCF rendering service.
        /// </summary>
        private RenderingCallback _callback;

        /// <summary>
        /// Set the rendering DPI
        /// </summary>
        public int DPI { get; set; }

        /// <summary>
        /// Get/Set the ImageQuality (0 -> 1.0).
        /// </summary>
        public double ImageQuality { get; set; }

        /// <summary>
        /// Where the talks should be ending up when they are rendered.
        /// </summary>
        public string TalkOutputDir { get; set; }

        /// <summary>
        /// The temp directory to be used for rendering
        /// </summary>
        public string TempRenderDir { get; set; }

        /// <summary>
        /// The actual client.
        /// </summary>
        private DTRenderClient _client;

        /// <summary>
        /// The workflow runtime.
        /// </summary>
        private WorkflowRuntime _wfRuntime;

        /// <summary>
        /// Tracks our work-readyness state.
        /// </summary>
        bool _readyForWork = false;

        /// <summary>
        /// Create a render workflow manager.
        /// </summary>
        public RenderWFManager()
        {
            _callback = new RenderingCallback(this);
        }

        /// <summary>
        /// Starts up the WF and manager
        /// </summary>
        public void Start()
        {
            ///
            /// Confifgure the callback
            /// 

            _callback.DPI = DPI;
            _callback.ImageQuality = ImageQuality;
            _callback.TalkOutputDir = TalkOutputDir;
            _callback.TempRenderDir = TempRenderDir;

            ///
            /// Start up the workflow, configure all of our services
            /// 

            _wfRuntime = new WorkflowRuntime();
            _wfRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(_wfRuntime_WorkflowCompleted);
            _wfRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(_wfRuntime_WorkflowTerminated);

            LongRunningActivityBase.RegisterService(_wfRuntime);

            _wfRuntime.StartRuntime();

            DeclareReadyForWork();
        }

        /// <summary>
        /// Declare ourselves ready to do some work.
        /// </summary>
        private void DeclareReadyForWork()
        {
            RetryCommunicationTillGood(() => SendReadyForWork(), "declaring ready for work");
        }

        /// <summary>
        /// Helper method ot avoid lambda capture!
        /// </summary>
        /// <returns></returns>
        private bool SendReadyForWork()
        {
            _client.ReadyForWork();
            _readyForWork = true;
            return true;
        }
 
        /// <summary>
        /// Configure the render client for work.
        /// </summary>
        private void SetupRenderClient()
        {
            lock (this)
            {
                ///
                /// Figure out if we have a good connection right now.
                /// 

                if (_client != null)
                {
                    if (_client.State != CommunicationState.Opened)
                    {
                        Console.WriteLine("--> Forgetting about a broken channel to IRender");
                        _client = null;
                    }
                    else
                    {
                        return;
                    }
                }

                ///
                /// Start the callback to WCF so we can listen for new talks to process.
                /// 

                Console.WriteLine("--> Creating the channel to the Render Client");
                _client = new DTRenderClient(new InstanceContext(_callback));
                _callback.Render = _client;
                _callback.Runtime = _wfRuntime;

                _client.InnerChannel.Faulted += new EventHandler(ChannelFactory_Closed);
                _client.InnerChannel.Closed += new EventHandler(ChannelFactory_Closed);
                Console.WriteLine("--> Render Client Channel created");

                ///
                /// If we are ready for work - make sure that everyone knows it so we get future
                /// things to work on!
                /// 

                if (_readyForWork)
                {
                    SendReadyForWork();
                }
            }
        }

        /// <summary>
        /// This isn't good -- try to reopen it!
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ChannelFactory_Closed(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(o => SetupRenderClient());
        }

        /// <summary>
        /// Workflow is done callback.
        /// </summary>
        /// <param name="e">Null if completed ok, otherwise it is the exception.</param>
        public delegate void WorkflowFinishedCallback(Exception e);

        public event WorkflowFinishedCallback WorkflowFinished;

        /// <summary>
        /// Called when we are done to fire the event.
        /// </summary>
        /// <param name="e"></param>
        protected void OnWorkflowFinished(Exception e)
        {
            if (WorkflowFinished != null)
            {
                WorkflowFinished(e);
            }
        }

        /// <summary>
        /// A workflow has "died". We should log it, I gues, and then get outta here.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _wfRuntime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
        {
            ///
            /// Render is not done... what do we do?
            /// 

            OnWorkflowFinished(e.Exception);

            ///
            /// Ok -- move on to the next one if there is one around!
            /// 

            DeclareReadyForWork();
        }

        /// <summary>
        /// A render was successfully completed. So now we can move on to the next dude.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _wfRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
        {
            ///
            /// Render is done! Wooo!
            /// 
            OnWorkflowFinished(null);

            ///
            /// Mark this talk as ready to do more work on!
            /// 

            MarkTalkRenderFinished((int)e.OutputParameters["TalkID"], (int)e.OutputParameters["NumberOfTalksRendered"]);

            ///
            /// Let them know we can do another now!
            /// 

            DeclareReadyForWork();
        }

        /// <summary>
        /// Keep trying to call attempt till it goes well. Do it however many times it takes, and wait
        /// for some time inbetween each attempt. Check the connection between calls.
        /// </summary>
        /// <param name="attempt"></param>
        private void RetryCommunicationTillGood(Func<bool> attempt, string message, int ticksToWait)
        {
            while (true)
            {
                try
                {
                    SetupRenderClient();
                    if (attempt())
                    {
                        return;
                    }
                    Console.WriteLine("Failed: " + message + " - will wait and try again");
                }
                catch (Exception e)
                {
                    Console.WriteLine("Failed: " + message + ": " + e.Message);
                    Console.WriteLine(e.StackTrace);
                }
                System.Threading.Thread.Sleep(ticksToWait);
            }
        }
        private void RetryCommunicationTillGood(Func<bool> attempt, string message)
        {
            RetryCommunicationTillGood(attempt, message, 10 * 1000);
        }

        /// <summary>
        /// Try to mark a talk as being finished!
        /// </summary>
        /// <param name="p"></param>
        private void MarkTalkRenderFinished(int id, int numberOfTalksRendered)
        {
            Console.WriteLine("  -> Render done for {0} - # of talks: {1}", id, numberOfTalksRendered);
            RetryCommunicationTillGood(() => MarkRenderDone(id, numberOfTalksRendered), "Marking render is done");
        }

        /// <summary>
        /// Helper method that avoids lambda capture (_client).
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        private bool MarkRenderDone(int id, int numberOfTalks)
        {
            _client.RenderDone(id, 100, numberOfTalks);
            return true;
        }

        /// <summary>
        /// Pretty much shuts everything down as quickly as possible.
        /// </summary>
        public void Stop()
        {
            ///
            /// Close down the WCF interaction
            /// 

            _client.Close();

            ///
            /// Now shut down the workflow
            /// 

            _wfRuntime.StopRuntime();
        }

        /// <summary>
        /// Ok - the callback has gotten a request to start some work, so we are
        /// now out of the ready-for-work state.
        /// </summary>
        internal void RenderStarted()
        {
            _readyForWork = false;
        }
    }
}
